package com.example.base_model.utils

import android.content.Context
import com.alibaba.sdk.android.oss.ClientConfiguration
import com.alibaba.sdk.android.oss.ClientException
import com.alibaba.sdk.android.oss.OSSClient
import com.alibaba.sdk.android.oss.ServiceException
import com.alibaba.sdk.android.oss.callback.OSSCompletedCallback
import com.alibaba.sdk.android.oss.common.OSSLog
import com.alibaba.sdk.android.oss.common.auth.OSSCredentialProvider
import com.alibaba.sdk.android.oss.common.auth.OSSStsTokenCredentialProvider
import com.alibaba.sdk.android.oss.model.PutObjectRequest
import com.alibaba.sdk.android.oss.model.PutObjectResult
import com.example.base_model.bean.general.OssUploadFileBean
import com.example.base_model.utils.OssUploadUtil.uploadFile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import java.io.File
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

/**
 * @Author: jiangKunKun
 * @CreateDate: 2025/10/17
 * @Description: OSS 文件上传工具类。
 *              提供单个文件同步上传（封装为挂起函数）和批量文件上传功能。
 * @SpittingGrooves:
 */
object OssUploadUtil {

    /**
     * 同步上传单个文件（通过 suspendCoroutine 封装成挂起函数）。
     * 此方法将异步的 OSS 上传操作包装成一个可挂起的协程函数，使其在协程中可以像同步代码一样调用。
     *
     * @param context Android 上下文，用于初始化 OSSClient。
     * @param ossUploadFileBean OSS 上传所需配置信息，包含 AccessKeyId、AccessKeySecret、SecurityToken、Host、BucketName、UploadFilePath 和 AliyunDomainDefault 等。
     * @param uploadFile 待上传的本地文件对象。
     * @return [Result<String>] 封装上传结果。成功时包含上传后的文件完整 URL，失败时包含异常信息。
     */
    suspend fun uploadFile(
        context: Context,
        ossUploadFileBean: OssUploadFileBean?,
        uploadFile: File
    ): Result<String> =
        suspendCoroutine { continuation ->
            // 启用 OSS 日志，方便调试
            OSSLog.enableLog()

            // 在移动端建议使用STS（Security Token Service）的方式初始化OSSClient，以提高安全性。
            // STS 临时凭证包含 AccessKeyId、AccessKeySecret 和 SecurityToken。
            val credentialProvider: OSSCredentialProvider = OSSStsTokenCredentialProvider(
                ossUploadFileBean?.accessKeyId, // <!----- 访问密钥ID。----->
                ossUploadFileBean?.accessKeySecret, // <!----- 访问密钥Secret。----->
                ossUploadFileBean?.securityToken // <!----- 安全令牌。----->
            )

            // 配置 OSS 客户端，例如超时时间、并发请求数和重试次数等。
            val conf = ClientConfiguration()
            conf.connectionTimeout = 15 * 1000 // <!----- 连接超时，默认15秒。----->
            conf.socketTimeout = 15 * 1000 // <!----- Socket超时，默认15秒。----->
            conf.maxConcurrentRequest = 5 // <!----- 最大并发请求数，默认5个。----->
            conf.maxErrorRetry = 2 // <!----- 失败后最大重试次数，默认2次。----->

            // 初始化 OSSClient 实例
            val ossClient = OSSClient(context, ossUploadFileBean?.host, credentialProvider, conf)

            // 检查待上传文件是否存在
            if (!uploadFile.exists()) {
                // 文件不存在，立即恢复协程并返回失败结果
                continuation.resume(Result.failure(Exception("文件不存在: $uploadFile")))
                return@suspendCoroutine
            }

            // 构建 PutObjectRequest，指定上传到 OSS 的文件信息
            val request = PutObjectRequest(
                ossUploadFileBean?.bucketName, // <!----- OSS 存储空间的名称（Bucket）。----->
                ossUploadFileBean?.uploadFilePath + uploadFile.getName(), // <!----- OSS 上的完整文件路径和名称（ObjectKey）。----->
                uploadFile.path // <!----- 本地待上传文件的绝对路径。----->
            )

            // 执行异步上传操作，并通过回调处理上传结果
            ossClient.asyncPutObject(
                request,
                object : OSSCompletedCallback<PutObjectRequest?, PutObjectResult?> {
                    /**
                     * 上传成功时回调。
                     * @param request 原始的上传请求对象。
                     * @param result 上传成功的结果对象。
                     */
                    override fun onSuccess(request: PutObjectRequest?, result: PutObjectResult?) {
                        // 上传成功，恢复协程并返回成功结果，包含上传后的文件完整 URL
                        continuation.resume(
                            Result.success(
                                ossUploadFileBean?.aliyunDomainDefault + "/" + request?.objectKey
                            )
                        )
                    }

                    /**
                     * 上传失败时回调。
                     * @param request 原始的上传请求对象。
                     * @param clientException 客户端异常，例如网络问题、参数错误等。
                     * @param serviceException 服务端异常，例如权限不足、Bucket 不存在等。
                     */
                    override fun onFailure(
                        request: PutObjectRequest?,
                        clientException: ClientException?,
                        serviceException: ServiceException?
                    ) {
                        // 上传失败，获取错误信息并恢复协程，返回失败结果
                        val error =
                            clientException?.message ?: serviceException?.message ?: "未知错误"
                        continuation.resume(Result.failure<String>(Exception(error)))
                    }
                })
        }

    /**
     * 批量上传多个文件（使用 async/awaitAll 确保顺序和完整性）。
     * 此函数在协程作用域内并行上传多个文件，并等待所有上传完成后返回结果。
     *
     * @param context Android 上下文。
     * @param ossUploadFileBean OSS 上传配置信息。
     * @param files 待上传的文件列表，每个元素是一个 Pair，包含 ObjectKey (OSS 目标文件名) 和本地文件对象。
     * @return [Pair<List<String>, List<Pair<String, String>>>]，
     *         第一个 List 包含所有成功上传的文件的 ObjectKey。
     *         第二个 List 包含所有上传失败的文件路径和其对应的 ObjectKey。
     */
    suspend fun uploadFiles(
        context: Context,
        ossUploadFileBean: OssUploadFileBean?,
        files: List<Pair<String, File>>
    ): Pair<List<String>, List<Pair<String, String>>> = coroutineScope {
        // 遍历文件列表，为每个文件创建一个异步上传任务
        val deferredList = files.map { (objectKey, file) ->
            async(Dispatchers.IO) { // 在 IO 调度器上启动异步任务，避免阻塞主线程
                val result = uploadFile(context, ossUploadFileBean, file) // 调用单个文件上传函数
                if (result.isSuccess) {
                    // 如果上传成功，返回 Success 包装器，包含 ObjectKey
                    ResultWrapper.Success(objectKey)
                } else {
                    // 如果上传失败，返回 Failure 包装器，包含文件路径、ObjectKey 和错误信息
                    ResultWrapper.Failure(file.path, objectKey, result.exceptionOrNull())
                }
            }
        }

        // 等待所有异步上传任务完成，并获取它们的 ResultWrapper 结果
        val results = deferredList.awaitAll()

        // 过滤出所有成功的上传结果，并映射为 ObjectKey 列表
        val successList = results.filterIsInstance<ResultWrapper.Success>().map { it.objectKey }
        // 过滤出所有失败的上传结果，并映射为文件路径和 ObjectKey 的 Pair 列表
        val failedList = results.filterIsInstance<ResultWrapper.Failure>()
            .map { Pair(it.filePath, it.objectKey) }

        // 返回成功和失败文件的列表
        Pair(successList, failedList)
    }

    /**
     * 内部密封类，用于封装单个文件上传的成功或失败结果。
     * 这有助于在批量上传时统一处理不同状态的结果。
     */
    private sealed class ResultWrapper {
        /**
         * 表示文件上传成功。
         * @param objectKey 上传到 OSS 后的文件 ObjectKey。
         */
        data class Success(val objectKey: String) : ResultWrapper()

        /**
         * 表示文件上传失败。
         * @param filePath 尝试上传的本地文件路径。
         * @param objectKey 尝试上传到 OSS 的目标 ObjectKey。
         * @param error 导致上传失败的异常信息。
         */
        data class Failure(val filePath: String, val objectKey: String, val error: Throwable?) :
            ResultWrapper()
    }

//    // 单个文件回调接口（如果需要，可以启用）
//    interface UploadCallback {
//        fun onSuccess(objectKey: String)
//        fun onFailure(objectKey: String, error: String?)
//    }
//
//    // 批量上传回调接口（如果需要，可以启用）
//    interface BatchUploadCallback {
//        fun onComplete(successList: List<String>, failedList: List<Pair<String, String>>)
//    }
}